#include "plot3d.hpp"
#include <iostream>
void Plot3dGrid::readGrid(const std::string &fname) {
  std::ifstream infile{fname, std::ios_base::binary};
  if (!infile) {
    std::cerr << "cannot open grid file" << std::endl;
    exit(EXIT_FAILURE);
  }
  const int blk_size = blks_.size();
  assert(blk_size != 0);
  std::vector<int> dummy_vec(5 + blk_size * 3);
  infile.read(reinterpret_cast<char *>(dummy_vec.data()),
              sizeof(int) * dummy_vec.size());
  int dummy, num_elem;
  for (int i = 0; i < blk_size; ++i) {
    std::shared_ptr<Plot3dBlk> &pblk = blks_[i];
    num_elem = pblk->blkSize();
    pblk->x.resize(num_elem);
    pblk->y.resize(num_elem);
    pblk->z.resize(num_elem);
    infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
    infile.read(reinterpret_cast<char *>(pblk->x.data()),
                sizeof(double) * num_elem);
    infile.read(reinterpret_cast<char *>(pblk->y.data()),
                sizeof(double) * num_elem);
    infile.read(reinterpret_cast<char *>(pblk->z.data()),
                sizeof(double) * num_elem);
    infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
  }
  infile.close();
}

void Plot3dGrid::readGridMetaData(const std::string &fname) {
  std::ifstream infile{fname, std::ios_base::binary};
  if (!infile) {
    std::cerr << "cannot open grid file" << std::endl;
    exit(EXIT_FAILURE);
  }
  int dummy, nblk;
  infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
  infile.read(reinterpret_cast<char *>(&nblk), sizeof(int));
  infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
  //   if (need_close)
  std::cout << "num blk = " << nblk << std::endl;

  std::vector<int> dims(nblk * 3);
  infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
  infile.read(reinterpret_cast<char *>(dims.data()), sizeof(int) * dims.size());
  infile.read(reinterpret_cast<char *>(&dummy), sizeof(int));
  infile.close();

  blks_.resize(nblk);
  int i3;
  for (int i = 0; i < nblk; ++i) {
    i3 = i * 3;
    blks_[i] = std::make_shared<Plot3dBlk>();
    blks_[i]->dims[0] = dims[i3];
    blks_[i]->dims[1] = dims[i3 + 1];
    blks_[i]->dims[2] = dims[i3 + 2];
  }
}

void Plot3dGrid::writeGrid(const std::string &fname) {
  std::ofstream ofile{fname, std::ios_base::binary};
  int dummy, blk_size = blks_.size();
  dummy = sizeof(int);
  ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));
  ofile.write(reinterpret_cast<char *>(&blk_size), sizeof(int));
  ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));

  std::vector<int> dims(blk_size * 3);
  int i3;
  for (int i = 0; i < blk_size; ++i) {
    i3 = i * 3;
    dims[i3] = blks_[i]->dims[0];
    dims[i3 + 1] = blks_[i]->dims[1];
    dims[i3 + 2] = blks_[i]->dims[2];
  }
  dummy = sizeof(int) * dims.size();
  ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));
  ofile.write(reinterpret_cast<char *>(dims.data()), sizeof(int) * dims.size());
  ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));

  int num_elem;
  for (int i = 0; i < blk_size; ++i) {
    std::shared_ptr<Plot3dBlk> &pblk = blks_[i];
    num_elem = pblk->blkSize();
    dummy = sizeof(double) * num_elem * 3;
    ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));
    ofile.write(reinterpret_cast<char *>(pblk->x.data()),
                sizeof(double) * num_elem);
    ofile.write(reinterpret_cast<char *>(pblk->y.data()),
                sizeof(double) * num_elem);
    ofile.write(reinterpret_cast<char *>(pblk->z.data()),
                sizeof(double) * num_elem);
    ofile.write(reinterpret_cast<char *>(&dummy), sizeof(int));
  }

  ofile.close();
}

std::ostream &operator<<(std::ostream &stream, const FaceBc &fbc) {
  constexpr int width = 8;
  stream << std::right << std::scientific << std::setprecision(5);
  stream << std::setw(width) << fbc.f_no << std::setw(width) << fbc.face
         << std::setw(width) << fbc.istart << std::setw(width) << fbc.iend
         << std::setw(width) << fbc.jstart << std::setw(width) << fbc.jend
         << std::setw(width) << fbc.kstart << std::setw(width) << fbc.kend
         << std::setw(width) << fbc.neighb << std::setw(width) << fbc.subface
         << std::setw(width) << fbc.ori << std::setw(14) << fbc.dtheta
         << std::endl;
  return stream;
}

void printFbcHead(std::ostream &os) {
  constexpr std::array<const char *, 12> head{
      "f_no",   "face", "istart", "iend",    "jstart", "jend",
      "kstart", "kend", "neighb", "subface", "ori",    "dtheta"};
  os << std::right;
  for (int i = 0; i < 11; ++i) {
    os.width(8);
    os << head[i];
  }
  os.width(14);
  os << head[11];

  os << std::endl;
}

void GridBcIn::print(const std::string &fname) const {
  std::ofstream os{fname};
  os << "Title of the simulations\n";
  os << "Blocks  Layers  Clusters\n";
  const int blk_size = blk_fbc_.size();
  os << "  " << blk_size << "  1   1\n";
  const std::string block_str = "Block";

  for (int i = 0; i < blk_size; ++i) {
    os << block_str << "  " << i + 1 << "\n";
    os << "Subfaces\n";
    const int fbc_size = blk_fbc_[i].size();
    os << "    " << fbc_size << "    1     1\n";
    printFbcHead(os);
    for (int k = 0; k < fbc_size; ++k)
      os << blk_fbc_[i][k];
  }
  os.close();
}

void GridBcIn::read(const std::string &fname) {
  std::ifstream infile{fname};
  if (!infile) {
    std::cerr << "cannot open grid file" << std::endl;
    exit(EXIT_FAILURE);
  }

  std::vector<char> dummy(1024);
  infile.getline(dummy.data(), dummy.size());
  infile.getline(dummy.data(), dummy.size());
  int nblk, tmp;
  infile >> nblk >> tmp >> tmp;
  std::cout << nblk << std::endl;
  blk_fbc_.resize(nblk);

  std::string buffer;
  int nfbc;
  for (int bi = 0; bi < nblk; ++bi) {
    infile >> buffer >> tmp;
    // std::cout << buffer << "  " << tmp << std::endl;
    assert(tmp == bi + 1);
    infile >> buffer;
    infile >> nfbc >> tmp >> tmp;
    infile.getline(dummy.data(), dummy.size());
    infile.getline(dummy.data(), dummy.size());
    blk_fbc_[bi].resize(nfbc);
    for (int k = 0; k < nfbc; ++k)
      infile >> blk_fbc_[bi][k];
  }
}

void GridBcIn::extend(const GridBcIn &rhs) {
  const int old_size = blk_fbc_.size();
  const int extend_size = rhs.blk_fbc_.size();
  const int new_size = old_size + extend_size;
  blk_fbc_.resize(new_size);

  for (int bi = 0; bi < extend_size; ++bi) {
    blk_fbc_[bi + old_size] = rhs.blk_fbc_[bi];
    std::vector<FaceBc> &fbc = blk_fbc_[bi + old_size];
    for (int k = 0; k < fbc.size(); ++k) {
      if (fbc[k].neighb > 0)
        fbc[k].neighb += old_size;
    }
  }
}

void Plot3dGrid::extend(Plot3dGrid *grids, const int num_grid) {
  for (int gi = 0; gi < num_grid; ++gi) {
    const int b_size = grids[gi].blkSize();
    for (int k = 0; k < b_size; ++k) {
      blks_.push_back(grids[gi].blks_[k]);
    }
  }
}

void checkBcInAndGrid(const GridBcIn &bcin, Plot3dGrid &grid) {
  if (bcin.blockSize() != grid.blkSize()) {
    std::cerr << "error: bcin.blockSize() != grid.blkSize()" << std::endl;
    exit(EXIT_FAILURE);
  }
  const int b_size = bcin.blockSize();
  for (int gi = 0; gi < b_size; ++gi) {
    int dims[3]{0, 0, 0};
    const std::vector<FaceBc> &blk_bc = bcin.blkBc(gi);
    auto blk = grid.blk(gi);
    for (int k = 0; k < blk_bc.size(); ++k) {
      dims[0] = std::max(dims[0], blk_bc[k].iend);
      dims[1] = std::max(dims[1], blk_bc[k].jend);
      dims[2] = std::max(dims[2], blk_bc[k].kend);
    }
    if (dims[0] != blk->dims[0] || dims[1] != blk->dims[1] ||
        dims[2] != blk->dims[2]) {
      std::cerr << "error: dims[0] != blk->dims[0] || dims[1] != blk->dims[1] "
                   "|| dims[2] != blk->dims[2]"
                << std::endl;
      exit(EXIT_FAILURE);
    }
  }
}

void combine(const GridBcIn *bcin, const int num_bcin, GridBcIn &out_bcin,
             Plot3dGrid *grids, const int num_grid, Plot3dGrid &out_grid) {
  if (num_bcin != num_grid) {
    std::cerr << "error: num_bcin != num_grid" << std::endl;
    exit(EXIT_FAILURE);
  }
  for (int i = 0; i < num_bcin; ++i)
    checkBcInAndGrid(bcin[i], grids[i]);

  for (int i = 0; i < num_bcin; ++i)
    out_bcin.extend(bcin[i]);
  out_grid.extend(grids, num_grid);
}

void GridBcIn::changeNeighbFromA2B(int a, int b) {
  for (int bi = 0; bi < blk_fbc_.size(); ++bi) {
    for (int k = 0; k < blk_fbc_[bi].size(); ++k)
      if (blk_fbc_[bi][k].neighb == a)
        blk_fbc_[bi][k].neighb = b;
  }
}

void initCombineOpts(const std::string &fname, CombineOpts &opt) {
  std::ifstream inp{fname};
  if (!inp) {
    std::cerr << "cannot open grid file" << std::endl;
    exit(EXIT_FAILURE);
  }
  exJson js;
  inp >> js;
  opt.bc_fname = js["bc_fname"].get<std::vector<std::string>>();
  opt.grid_fname = js["grid_fname"].get<std::vector<std::string>>();
  //   opt.only_process_neighb = js["only_process_neighb"].get<bool>();
  opt.neighb_a2b = js["neighb_a2b"].get<std::vector<int>>();
  opt.out_bc_fname = js["out_bc_fname"].get<std::string>();
  opt.out_grid_fname = js["out_grid_fname"].get<std::string>();

  int flag{0};
  flag += (opt.neighb_a2b.size() != 2);
  flag += (opt.neighb_a2b.size() != 0);
  if (flag == 2) {
    std::cerr << "opt.neighb_a2b.size() != 2 || opt.neighb_a2b.size() != 0"
              << std::endl;
    exit(EXIT_FAILURE);
  }
  if (opt.bc_fname.size() != opt.grid_fname.size() ||
      opt.bc_fname.size() == 0) {
    std::cerr << "opt.bc_fname.size() != opt.grid_fname.size() || "
                 "opt.bc_fname.size() == 0"
              << std::endl;
    exit(EXIT_FAILURE);
  }
  inp.close();
}